package cn.zz.veggie.application.service;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.zz.comm.core.types.query.PageQuery;
import cn.zz.comm.core.types.result.PageResult;
import cn.zz.comm.helper.BizCommHelper;
import cn.zz.comm.tool.AsyncTool;
import cn.zz.comm.util.CollectorUtil;
import cn.zz.comm.util.DateTimeUtil;
import cn.zz.veggie.application.assembler.OrderAssembler;
import cn.zz.veggie.application.helper.OrderHelper;
import cn.zz.veggie.application.packer.OrderPacker;
import cn.zz.veggie.core.enums.biz.OrderStatus;
import cn.zz.veggie.model.domain.DelMan;
import cn.zz.veggie.model.domain.Order;
import cn.zz.veggie.model.domain.OrderInfo;
import cn.zz.veggie.model.domain.Product;
import cn.zz.veggie.model.domain.Shop;
import cn.zz.veggie.model.query.OrderInfoQuery;
import cn.zz.veggie.model.query.OrderQuery;
import cn.zz.veggie.model.sort.OrderSort;
import cn.zz.veggie.persistence.repository.DelManRepository;
import cn.zz.veggie.persistence.repository.OrderInfoRepository;
import cn.zz.veggie.persistence.repository.OrderRepository;
import cn.zz.veggie.persistence.repository.ProductRepository;
import cn.zz.veggie.persistence.repository.SequenceRepository;
import cn.zz.veggie.persistence.repository.ShopQuoteRepository;
import cn.zz.veggie.persistence.repository.ShopRepository;
import cn.zz.veggie.web.types.dto.mgmt.order.MgmtOrderAppendDto;
import cn.zz.veggie.web.types.dto.mgmt.order.MgmtOrderCreateDto;
import cn.zz.veggie.web.types.dto.mgmt.order.MgmtOrderItemRemoveDto;
import cn.zz.veggie.web.types.dto.mgmt.order.MgmtOrderItemSaveDto;
import cn.zz.veggie.web.types.dto.mgmt.order.MgmtOrderQueryDto;
import cn.zz.veggie.web.types.dto.mgmt.order.MgmtOrderSaveDto;
import cn.zz.veggie.web.types.dto.mgmt.order.MgmtOrderStatusDto;
import cn.zz.veggie.web.types.vo.mgmt.MgmtOrderDetailVo;
import cn.zz.veggie.web.types.vo.mgmt.MgmtOrderVo;
import cn.zz.veggie.web.types.vo.mgmt.order.MgmtOrderBriefVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author zhongq
 * @since 2024/5/25
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderAppService {

	private final OrderRepository orderRepository;
	private final OrderInfoRepository orderInfoRepository;
	private final ProductRepository productRepository;
	private final ShopQuoteRepository shopQuoteRepository;
	private final SequenceRepository sequenceRepository;
	private final DelManRepository delManRepository;
	private final ShopRepository shopRepository;
	private final AsyncTool asyncTool;

	public PageResult<MgmtOrderVo> adminGetList(MgmtOrderQueryDto dto, PageQuery pageQuery) {
		OrderQuery query = OrderAssembler.buildAdminQuery(dto);
		List<Order> orders = orderRepository.findByPage(query, pageQuery);
		Set<Integer> shopIds = CollectorUtil.bws(orders, Order::getShopId);
		Map<Integer, Shop> shopMap = shopRepository.findByIds(shopIds);
		Set<Integer> delManIds = CollectorUtil.bws(orders, Objects::nonNull, Order::getDelManId);
		final Map<Integer, DelMan> delManMap = delManRepository.findByIds(delManIds);
		List<MgmtOrderVo> vos = CollectorUtil.bwl(orders, o -> {
			final Shop shop = shopMap.get(o.getShopId());
			final DelMan delMan = delManMap.get(o.getDelManId());
			return OrderPacker.toMgmtOrderVo(o, shop, delMan);
		});
		return PageResult.of(pageQuery, vos);
	}

	public List<MgmtOrderBriefVo> mgmtGetRecent(Integer size) {
		final OrderQuery query = new OrderQuery();
		query.status = OrderStatus.active.status;
		query.sort = OrderSort.idDesc;
		final PageQuery page = PageQuery.size(size).setSearchCount(false);
		List<Order> orders = orderRepository.findByPage(query, page);
		Set<Integer> shopIds = CollectorUtil.bws(orders, Order::getShopId);
		Map<Integer, Shop> shopMap = shopRepository.findByIds(shopIds);
		return CollectorUtil.bwl(orders, o -> {
			final Shop shop = shopMap.get(o.getShopId());
			return OrderPacker.toMgmtOrderBriefVo(o, shop);
		});
	}

	public MgmtOrderDetailVo adminGetDetail(Integer id) {
		Order order = orderRepository.findById(id);
		BizCommHelper.requireNoNull(order, String.valueOf(id));
		List<OrderInfo> orderItems = orderInfoRepository.findByPage(OrderInfoQuery.ofOrderId(id), PageQuery.all());
		Shop shop = shopRepository.findById(order.getShopId());
		List<Integer> productIds = CollectorUtil.bwl(orderItems, OrderInfo::getProductId);
		Map<Integer, Product> productMap = productRepository.findByIds(productIds);
		DelMan delMan = delManRepository.findById(order.getDelManId());
		return OrderPacker.toOrderDetail(order, shop, orderItems, productMap, delMan);
	}

	public void mgmtSaveOrder(MgmtOrderSaveDto dto) {
		final Integer orderId = dto.getId();
		Order order = orderRepository.findById(orderId);
		OrderHelper.requireNoNull(order, orderId);
		final Integer delManId = dto.getDelManId();
		final DelMan delMan = delManRepository.findById(delManId);
		BizCommHelper.requireNoNull(delMan, delManId);
		OrderAssembler.buildUpdate(order, dto);
		orderRepository.save(order);
	}

	@Transactional
	public void adminCreate(MgmtOrderCreateDto dto) {
		final Integer uid = dto.getShopId();
		final Shop shop = shopRepository.findById(uid);
		BizCommHelper.requireNoNull(shop, "shop", uid);
		final Map<Integer, Product> productMap = productRepository.findByIds(dto.collectProductIds());
		dto.requireValid(productMap.keySet());
		// valid product
		final String orderSn = sequenceRepository.genOrderSn(uid);
		Order order = OrderAssembler.createOrder(shop, dto, orderSn);
		orderRepository.save(order);
		// build order item
		List<OrderInfo> items = CollectorUtil.bwl(dto.getItems(), item -> OrderAssembler.createOrderItem(order, item));
		orderInfoRepository.saveBatch(items);
		// quote
		syncProductQuote(uid, items, productMap);
	}

	@Transactional
	public void adminOrderAppend(MgmtOrderAppendDto dto) {
		Order order = orderRepository.findOne(OrderQuery.ofSn(dto.getOrderNo()));
		OrderHelper.requireNoNull(order, dto.getOrderNo());
		OrderHelper.requireMatchUser(dto.getShopId(), order);
		orderRepository.save(order.changeAmount(dto.calcAmount()));
		final Map<Integer, Product> productMap = productRepository.findByIds(dto.collectProductIds());
		dto.requireValid(productMap.keySet());
		List<OrderInfo> items = CollectorUtil.bwl(dto.getItems(), item -> OrderAssembler.createOrderItem(order, item));
		orderInfoRepository.saveBatch(items);
		// save quote
		syncProductQuote(order.getShopId(), items, productMap);
	}

	public void adminChangeStatus(MgmtOrderStatusDto dto) {
		Collection<Order> orders = orderRepository.findByIds(dto.getIds()).values();
		dto.requireValid(orders);
		// change status
		orderRepository.updateStatus(dto.getIds(), dto.getStatus());
	}

	@Transactional
	public void adminSaveOrderItem(MgmtOrderItemSaveDto dto) {
		Order order = orderRepository.findById(dto.getId());
		OrderHelper.requireNoNull(order, dto.getId());
		int oldAmount = Optional.ofNullable(dto.getId()).map(itemId -> {
			OrderInfo orderItem = orderInfoRepository.findById(itemId);
			OrderHelper.requireNoNull(orderItem, itemId);
			return orderItem.getAmount();
		}).orElse(0);
		OrderInfo orderItem = OrderAssembler.buildOrderItem(order, dto);
		// order item
		orderInfoRepository.save(orderItem);
		// order
		orderRepository.save(order.changeAmount(dto.calcAmount() - oldAmount));
		// quote
		shopQuoteRepository.updateOrSaveQuotes(order.getShopId(), Map.of(dto.getProductId(), dto.getQuote()));
	}

	@Transactional
	public void adminReCalcAmount(Integer id) {
		Order order = orderRepository.findById(id);
		OrderHelper.requireNoNull(order, id);
		List<OrderInfo> orderItems = orderInfoRepository.findByPage(OrderInfoQuery.ofOrderId(id), PageQuery.all());
		int totalAmount = orderItems.stream().mapToInt(item -> {
			Integer amount = item.calcAmount();
			if (Objects.equals(amount, item.getAmount())) {
				return amount;
			}
			item.setAmount(amount);
			item.setUpdatedAt(DateTimeUtil.unixTime());
			orderInfoRepository.save(item);
			return amount;
		}).sum();
		if (!Objects.equals(order.getAmount(), totalAmount)) {
			order.setAmount(totalAmount);
			order.setUpdatedAt(DateTimeUtil.unixTime());
		}
	}

	@Transactional
	public void mgmtItemRemove(MgmtOrderItemRemoveDto dto) {
		final Order order = orderRepository.findById(dto.getOrderId());
		OrderHelper.requireNoNull(order, dto.getOrderId());
		final Integer itemId = dto.getItemId();
		final OrderInfo orderInfo = orderInfoRepository.findById(itemId);
		OrderHelper.requireNoNull(orderInfo, itemId);
		final Integer amount = orderInfo.getAmount();
		order.changeAmount(-amount);
		orderInfoRepository.deleteById(itemId);
		orderRepository.save(order);
	}

	private void syncProductQuote(Integer shopId, List<OrderInfo> items, Map<Integer, Product> productMap) {
		// save quote
		asyncTool.task(() -> {
			// diff
			Map<Integer, Integer> currQuoteMap = CollectorUtil.btm(items, oi -> {
				final Integer productId = oi.getProductId();
				final Integer price = oi.getPrice();
				return !Objects.equals(price, productMap.get(productId).getPrice());
			}, OrderInfo::getProductId, OrderInfo::getPrice);
			shopQuoteRepository.updateOrSaveQuotes(shopId, currQuoteMap);
		});
	}

	public void mgmtRemoveOrder(Integer id) {
		final Order order = orderRepository.findById(id);
		OrderHelper.requireNoNull(order, id);
		orderRepository.save(order.delete());
	}

}